home *** CD-ROM | disk | FTP | other *** search
Wrap
#!/usr/bin/env python ############################################################################# ## ## Copyright 2007-2008 Canonical Ltd ## Author: Jonathan Riddell <jriddell@ubuntu.com> ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of ## the License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see <http://www.gnu.org/licenses/>. ## ############################################################################# import sys, os, time from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4 import uic from PyKDE4.kdecore import * from PyKDE4.kdeui import * from PyKDE4.kio import * import apt_check def translate(self, prop): """reimplement method from uic to change it to use gettext""" if prop.get("notr", None) == "true": return self._cstring(prop) else: if prop.text is None: return "" text = prop.text.encode("UTF-8") return i18n(text) uic.properties.Properties._string = translate from UpdateManager.Core.MetaRelease import MetaReleaseCore from UpdateManager.DistUpgradeFetcherKDE import DistUpgradeFetcherKDE import time class App(QObject): """an applet to show a systray icon when apt has software updates to be installed, when Apport has crash reports, for reboot notification and for upgrade hook messages""" def __init__(self, distUpgrade=False, useDevelopmentRelease=False, useProposed=False): QObject.__init__(self) if distUpgrade or useDevelopmentRelease or useProposed: self.upgradeMode = True self.newReleaseCheck(useDevelopmentRelease, useProposed) else: self.upgradeMode = False self.init() def init(self): self.tray = KSystemTrayIcon(KIcon("system-software-update")) self.connect(self.tray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.activated) self.dirWatch = KDirWatch(self) self.dirWatch.addDir("/var/lib/apt/lists/") #apt-get update has been run self.dirWatch.addFile("/var/lib/update-notifier/dpkg-run-stamp") #something has been installed self.connect(self.dirWatch, SIGNAL("dirty(const QString &)"), self.aptDirectoryChanged) QTimer.singleShot(5000, self.aptDirectoryChanged) #run check shortly after startup try: pipe = os.popen("lsb_release --id -s") self.distroName = pipe.read().strip() del pipe if self.distroName == "Ubuntu": self.distroName = "Kubuntu" except Exception, e: self.distroName = i18nc("fallback distro name", "distribution release") self.rebootTray = KSystemTrayIcon(KIcon("system-restart")) self.connect(self.rebootTray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.rebootActivated) self.rebootWatch = KDirWatch(self) self.rebootWatch.addDir("/var/run/") self.rebootWatch.addDir("/var/lib/update-notifier/") self.connect(self.rebootWatch, SIGNAL("dirty(const QString &)"), self.rebootRequired) if QFile.exists("/usr/share/apport/apport-qt"): self.apportWatch = KDirWatch(self) self.apportWatch.addDir("/var/crash/") self.connect(self.apportWatch, SIGNAL("dirty(const QString &)"), self.runApport) self.showApportIcon() self.hooksTray = KSystemTrayIcon(KIcon("help-hint")) self.hooksWatch = KDirWatch(self) self.hooksWatch.addDir("/var/lib/update-notifier/user.d/") self.connect(self.hooksWatch, SIGNAL("dirty(const QString &)"), self.processUpgradeHooks) self.processUpgradeHooks() def newReleaseCheck(self, useDevelopmentRelease, useProposed): """ check for updates when user asks for --devel-release or --proposed, if there are any show the wizard """ metaRelease = MetaReleaseCore(useDevelopmentRelease, useProposed) while metaRelease.downloading: time.sleep(1) self.new_dist = metaRelease.new_dist self.trayToolTip = "" showTray = False if self.new_dist is not None: self.fetcher = DistUpgradeFetcherKDE(useDevelopmentRelease, useProposed) #runs sys.exit() if no result QTimer.singleShot(10, self.runDistUpgrade) else: KMessageBox.sorry(None, i18n("No new upgrade available"), i18n("No Upgrade")) sys.exit() def runDistUpgrade(self): result = self.fetcher.run() if self.upgradeMode == True and (result is None or result == False): sys.exit() #update checking def aptDirectoryChanged(self, path=None): """ check for updates, if there are any show the tray icon """ metaRelease = MetaReleaseCore() while metaRelease.downloading: time.sleep(1) self.new_dist = metaRelease.new_dist # a previous run may have enabled tooltip. reset if not visible only if not self.tray.isVisible(): self.trayToolTip = "" showTray = False if self.new_dist is not None: self.trayToolTip = i18nc("distro name, version number", "An upgrade to %1 %2 is available.", self.distroName, self.new_dist.version) showTray = True menu = self.tray.contextMenu() if not self.tray.isVisible(): upgradeAction = menu.addAction(i18nc("distro name, version number", "Upgrade to %1 %2", self.distroName, self.new_dist.version)) self.connect(upgradeAction, SIGNAL("triggered(bool)"), self.upgradeActionTriggered) result = apt_check.run() if result[1] > 0: #security updates available, show different icon? pass if result[0] > 0: showTray = True if not self.tray.isVisible(): self.updates = result[0] QTimer.singleShot(1000, self.showMessage) #delay so it is shown when tray icon in correct position self.trayToolTip = i18np("A software update is available", "%1 software updates are available", self.updates) if self.new_dist is not None: self.trayToolTip = i18np("A software update is available.\nAn upgrade to %2 %3 is also available.", "%1 software updates are available.\nAn upgrade to %2 %3 is also available.", self.updates, self.distroName, self.new_dist.version) self.tray.setToolTip(self.trayToolTip) self.tray.setVisible(showTray) def showMessage(self): # FIXME can only use KPassivePopup as a static method with systray but that means # we can't connect to the clicked() slot KPassivePopup.message(KPassivePopup.Balloon, i18n("Software Updates"), self.trayToolTip, KIcon("system-software-update").pixmap(QSize(22,22)), self.tray) def upgradeActionTriggered(self, checked): """called from tray icon right click menu""" self.fetcher = DistUpgradeFetcherKDE() QTimer.singleShot(10, self.runDistUpgrade) def activated(self, activationReason): """run adept when icon is clicked on""" if activationReason == QSystemTrayIcon.Trigger: doingDistUpgrade = False if self.new_dist is not None: distUpgrade = KMessageBox.questionYesNo(None, i18n("Do you want to do a full upgrade to the new distribution release %1?", self.new_dist.version), i18n("Full Upgrade?"), KStandardGuiItem.yes(), KStandardGuiItem.no(), "distUpgrade") if distUpgrade == KMessageBox.Yes: doingDistUpgrade = True self.fetcher = DistUpgradeFetcherKDE() QTimer.singleShot(10, self.runDistUpgrade) if not doingDistUpgrade: KProcess.startDetached("kdesudo", ["adept", "updater"]) #reboot notification def rebootRequired(self, path=None): if QFile.exists("/var/run/reboot-required") and QFile.exists("/var/lib/update-notifier/dpkg-run-stamp") and not self.rebootTray.isVisible(): self.rebootTray.show() QTimer.singleShot(1000, self.showRebootMessage) #delay so it is shown when tray icon in correct position self.rebootTray.setToolTip(i18n("Newly updated software needs your system to be rebooted before it can be used.")) def showRebootMessage(self): # FIXME can only use KPassivePopup as a static method with systray but that means # we can't connect to the clicked() slot KPassivePopup.message(KPassivePopup.Balloon, i18n("Reboot Required"), i18n("Newly updated software needs your system to be rebooted before it can be used."), KIcon("system-restart").pixmap(QSize(22,22)), self.rebootTray) def rebootActivated(self, activationReason): if activationReason == QSystemTrayIcon.Trigger: KProcess.startDetached("qdbus", ["org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface.logout", "1", "1", "3"]) #ShutdownConfirmYes ShutdownTypeReboot ShutdownModeInteractive - see README.kworkspace for other possibilities # Apport def runApport(self): time.sleep(3) #sleep briefly, else we are too fast for apport result = os.system("/usr/share/apport/apport-checkreports --system") if result == 0: KProcess.startDetached("kdesudo", ["/usr/share/apport/apport-qt"]) else: KProcess.startDetached("/usr/share/apport/apport-qt") # make sure we hide something that exists if self.apportTray is not None: self.apportTray.hide() def showApportIcon(self): """ run on startup, show apport icon if crashes are outstanding """ result = os.system("/usr/share/apport/apport-checkreports") if result == 0: self.apportTray = KSystemTrayIcon(KIcon("apport")) self.connect(self.apportTray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.apportActivated) self.apportTray.show() QTimer.singleShot(2000, self.showApportMessage) #delay so it is shown when tray icon in correct position self.crashMessage = i18n("An application has crashed on your " "system (now or in the past).\n" "Click to " "display details. ") self.apportTray.setToolTip(self.crashMessage) else: # prevent AttributeError exception if apportTray was not created above self.apportTray = None def showApportMessage(self): # FIXME can only use KPassivePopup as a static method with systray but that means # we can't connect to the clicked() slot KPassivePopup.message(KPassivePopup.Balloon, i18n("Crash Handler"), self.crashMessage, KIcon("apport").pixmap(QSize(22,22)), self.apportTray) def apportActivated(self, activationReason): if activationReason == QSystemTrayIcon.Trigger: self.runApport() #upgrade hooks, see https://wiki.kubuntu.org/InteractiveUpgradeHooks def processUpgradeHooks(self): """show systray icon if upgrade hook messages need to be shown""" files = os.listdir("/var/lib/update-notifier/user.d/") self.fileInfos = {} for file in files: fileResult = self.processUpgradeHook(file) if fileResult != None: self.fileInfos[file] = fileResult self.fileInfos[file]["fileName"] = file if len(self.fileInfos) > 0: self.connect(self.hooksTray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.hooksActivated) self.hooksTray.setToolTip(i18n("Software upgrade notifications are available")) self.hooksTray.show() QTimer.singleShot(2000, self.showHooksMessage) #delay so it is shown when tray icon in correct position def processUpgradeHook(self, file): """parse an upgrade hook file and return it if it should be shown""" fileHandle = open("/var/lib/update-notifier/user.d/" + file) fileInfo = {} for line in fileHandle: colon = line.find(":") space = line.find(" ") if (colon > 0 and space == -1) or (colon != -1 and colon < space): list = line.split(":", 1) key = list[0] fileInfo[key] = list[1].strip() else: fileInfo[key] += line #check if seen already config = KGlobal.config() configGroup = config.group("updateNotifications") alreadySeen = configGroup.readEntry(file, QVariant(False)) if alreadySeen.toBool(): return #check if before a reboot if "DontShowAfterReboot" in fileInfo and fileInfo["DontShowAfterReboot"] == "True": uptimeFile = open("/proc/uptime") uptimeLine = uptimeFile.readline() uptime = float(uptimeLine.split(" ")[0]) now = time.time() stat = os.stat("/var/lib/update-notifier/user.d/" + file) #if((int)uptime > 0 && (now - mtime) > (int)uptime) { then skip if uptime > 0 and ((now - stat.st_mtime) > uptime): return #check if DisplayIf true if "DisplayIf" in fileInfo: display = os.system(fileInfo["DisplayIf"]) if display != 0: return #we want to show this one, return the dictionary return fileInfo def showHooksMessage(self): # FIXME can only use KPassivePopup as a static method with systray but that means # we can't connect to the clicked() slot KPassivePopup.message(KPassivePopup.Balloon, i18n("Upgrade Information"), i18n("Software upgrade notifications are available"), KIcon("help-hint").pixmap(QSize(22,22)), self.hooksTray) def hooksActivated(self, activationReason): """show the dialogue with messages when user clicks on upgrade hooks systray icon""" if activationReason == QSystemTrayIcon.Trigger: self.hooksTray.hide() dialogue = QDialog() if os.path.exists("hooks-dialogue.ui"): APPDIR = QDir.currentPath() else: file = KStandardDirs.locate("appdata", "hooks-dialogue.ui") APPDIR = file.left(file.lastIndexOf("/")) uic.loadUi(APPDIR + "/hooks-dialogue.ui", dialogue) dialogue.image.setPixmap(KIcon("help-hint").pixmap(QSize(48,48))) nextButton = dialogue.buttonBox.addButton(i18n("Next"), QDialogButtonBox.AcceptRole) nextButton.setIcon(KIcon("go-next")) runButton = dialogue.buttonBox.addButton(i18n("Run this action now"), QDialogButtonBox.ActionRole) runButton.setIcon(KIcon("system-run")) self.connect(runButton, SIGNAL("clicked()"), self.runHookCommand) dialogue.buttonBox.button(QDialogButtonBox.Close).setIcon(KIcon("dialog-close")) i = 0 for file in self.fileInfos: i = i + 1 if len(self.fileInfos) == i: nextButton.hide() self.terminal = False if self.fileInfos[file].has_key("Command"): runButton.show() self.command = self.fileInfos[file]["Command"] if self.fileInfos[file].has_key("Terminal") and self.fileInfos[file]["Terminal"] == "True": self.terminal = True else: runButton.hide() language = KGlobal.locale().language() if self.fileInfos[file].has_key("Name"): if self.fileInfos[file].has_key("Name-" + language): text = self.fileInfos[file]["Name" + language] else: text = self.fileInfos[file]["Name"] dialogue.vbox_message.setText(i18n("<b>%1</b>", text)) else: dialogue.vbox_message.setText("<b>Update Information</b>") if self.fileInfos[file].has_key("Description-" + language): text = self.fileInfos[file]["Description" + language] else: text = self.fileInfos[file]["Description"] text = text.replace("\n", "") if self.fileInfos[file].has_key("GettextDomain"): KGlobal.locale().insertCatalog(self.fileInfos[file]["GettextDomain"]) text = i18n(text) dialogue.textview_hook.setText(text) result = dialogue.exec_() #save that it has been seen config = KGlobal.config() configGroup = config.group("updateNotifications") configGroup.writeEntry(file, QVariant(True)) config.sync() if result == QDialog.Rejected: break def runHookCommand(self): process = KProcess() if self.terminal: self.command = "konsole -e " + self.command process.setShellCommand(self.command) process.startDetached() if __name__ == "__main__": """start the application. TODO, clever things here to not start the GUI until it has to""" appName = "update-notifier-kde" catalogue = "update-notifier-kde" programName = ki18n("Update Notifier") version = "1.1" description = ki18n("An applet to show a systray icon when apt has software updates to be installed, when Apport has crash reports, for reboot notification and for upgrade hook messages.") license = KAboutData.License_GPL copyright = ki18n("2008 Canonical Ltd") text = KLocalizedString() homePage = "http://launchpad.net/adept" bugEmail = "" aboutData = KAboutData(appName, catalogue, programName, version, description, license, copyright, text, homePage, bugEmail) aboutData.addAuthor(ki18n("Jonathan Riddell"), ki18n("Author")) options = KCmdLineOptions() options.add("d").add("devel-release", ki18n("Offer to upgrade to the latest devel release, if possible")) options.add("p").add("proposed", ki18n("Offer to upgrade using latest proposed version of the release upgrader, if possible")) options.add("u").add("dist-upgrade", ki18n("Offer to upgrade to the latest release, if possible")) KCmdLineArgs.init(sys.argv, aboutData) KCmdLineArgs.addCmdLineOptions(options) app = KApplication() app.setQuitOnLastWindowClosed(False) app.setWindowIcon(KIcon("system-software-update")) if app.isSessionRestored(): sys.exit(1) args = KCmdLineArgs.parsedArgs() distUpgrade = False useDevelopmentRelease = False useProposed = False if args.isSet("dist-upgrade"): distUpgrade = True if args.isSet("devel-release"): useDevelopmentRelease = True if args.isSet("proposed"): useProposed = True applet = App(distUpgrade, useDevelopmentRelease, useProposed) sys.exit(app.exec_())